﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenMvvmToolkit.Attributes;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkitTest.TestInfrastructure;
using MugenMvvmToolkitTest.TestModels;
using Should;
using System.Linq;

namespace MugenMvvmToolkitTest.Infrastructure
{
    public class SpyAttribute : ValidationAttribute
    {
        #region Properties

        public static ValidationContext ValidationContext { get; set; }

        #endregion

        #region Overrides of ValidationAttribute

        /// <summary>
        /// Validates the specified value with respect to the current validation attribute.
        /// </summary>
        /// <returns>
        /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult"/> class. 
        /// </returns>
        /// <param name="value">The value to validate.</param><param name="validationContext">The context information about the validation operation.</param>
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            ValidationContext = validationContext;
            return ValidationResult.Success;
        }

        #endregion
    }

    public class SimpleValidatableModel
    {
        [Required]
        public string Property { get; set; }

        [Required, Display(Name = DataAnnotationValidationProviderTest.DisplayName)]
        public string SecondProperty { get; set; }
    }

    [MugenMvvmToolkit.Attributes.MetadataType(typeof(MetadataValue))]
    public class MetadataValidatableModel
    {
        public class MetadataValue
        {
            [Required, Display(Name = DataAnnotationValidationProviderTest.DisplayName)]
            public string Property { get; set; }
        }

        public string Property { get; set; }
    }

    public class ContextValidatableModel
    {
        [Spy, Display(Name = DataAnnotationValidationProviderTest.DisplayName)]
        public string SpyProperty { get; set; }
    }

    public class DuplicatePropertyValidatableModel : SimpleValidatableModel
    {
        [Required]
        public new string Property { get; set; }
    }

    //TODO REWRITE TESTS
    [TestClass]
    public class DataAnnotationValidationProviderTest
    {
        public const string DisplayName = "DisplayName";

        [TestMethod]
        public void ValidateObjectTest()
        {
            var errors = new List<IValidationResult>();
            var model = new SimpleValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), null);
            provider.ValidateObject(model, false, errors, null);
            errors.Count.ShouldEqual(2);

            errors.Any(result => result.MemberNames.Contains("Property")).ShouldBeTrue();
            errors.Any(result => result.MemberNames.Contains("SecondProperty")).ShouldBeTrue();

            model.Property = DisplayName;
            model.SecondProperty = DisplayName;

            errors.Clear();
            provider.ValidateObject(model, false, errors, null);
            errors.Count.ShouldEqual(0);
        }

        [TestMethod]
        public void ValidateObjectOnceErrorTest()
        {
            var errors = new List<IValidationResult>();
            var model = new SimpleValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), null);
            provider.ValidateObject(model, true, errors, null);
            errors.Count.ShouldEqual(1);

            errors.Any(result => result.MemberNames.Contains("Property")).ShouldBeTrue();

            model.Property = DisplayName;
            model.SecondProperty = DisplayName;

            errors.Clear();
            provider.ValidateObject(model, true, errors, null);
            errors.Count.ShouldEqual(0);
        }

        [TestMethod]
        public void ValidatePropertyTest()
        {
            var errors = new List<IValidationResult>();
            var model = new SimpleValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), null);
            provider.ValidateProperty(model, "Property", errors, null);
            errors.Count.ShouldEqual(1);
            errors.Any(result => result.MemberNames.Contains("Property")).ShouldBeTrue();

            errors.Clear();
            provider.ValidateProperty(model, "SecondProperty", errors, null);
            errors.Count.ShouldEqual(1);
            errors.Any(result => result.MemberNames.Contains("SecondProperty")).ShouldBeTrue();

            errors.Clear();
            provider.ValidateProperty(model, "Empty", errors, null);
            errors.Count.ShouldEqual(0);
        }

        [TestMethod]
        public void ValidateObjectWithMetadaTest()
        {
            var errors = new List<IValidationResult>();
            var model = new MetadataValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), null);
            provider.ValidateObject(model, false, errors, null);
            errors.Count.ShouldEqual(1);

            errors.Any(result => result.MemberNames.Contains("Property")).ShouldBeTrue();
            model.Property = DisplayName;

            errors.Clear();
            provider.ValidateObject(model, false, errors, null);
            errors.Count.ShouldEqual(0);
        }

        [TestMethod]
        public void ValidateObjectWithMetadataPropertyTest()
        {
            var errors = new List<IValidationResult>();
            var model = new MetadataValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), null);
            provider.ValidateProperty(model, "Property", errors, null);
            errors.Count.ShouldEqual(1);
            errors.Any(result => result.MemberNames.Contains("Property")).ShouldBeTrue();

            errors.Clear();
            provider.ValidateProperty(model, "Empty", errors, null);
            errors.Count.ShouldEqual(0);
        }

        [TestMethod]
        public void ValidateObjectWithContextTest()
        {
            var metadata = new Dictionary<object, object>();
            IServiceProvider adapter = new TestIocAdapter() { GetFunc = (type, s, arg3) => DisplayName };
            var errors = new List<IValidationResult>();
            var model = new ContextValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), adapter);

            provider.ValidateObject(model, false, errors, metadata);
#if !NETFX_CORE
            SpyAttribute.ValidationContext.Items.ShouldEqual(metadata);
            SpyAttribute.ValidationContext.GetService(typeof(string)).ShouldEqual(DisplayName);
#else
            var serviceProvider = (IServiceProvider)SpyAttribute.ValidationContext.Items[DataAnnotationValidationProvider.ServiceProviderKey];
            serviceProvider.GetService(typeof(string)).ShouldEqual(DisplayName);
#endif
            SpyAttribute.ValidationContext.ObjectType.ShouldEqual(typeof(ContextValidatableModel));
            SpyAttribute.ValidationContext.ObjectInstance.ShouldEqual(model);
            SpyAttribute.ValidationContext.DisplayName.ShouldEqual(DisplayName);
        }

        [TestMethod]
        public void ValidatePropertyWithContextTest()
        {
            var metadata = new Dictionary<object, object>();
            IServiceProvider adapter = new TestIocAdapter { GetFunc = (type, s, arg3) => DisplayName };
            var errors = new List<IValidationResult>();
            var model = new ContextValidatableModel();
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), adapter);

            provider.ValidateProperty(model, "SpyProperty", errors, metadata);
#if !NETFX_CORE
            SpyAttribute.ValidationContext.Items.ShouldEqual(metadata);
            SpyAttribute.ValidationContext.GetService(typeof(string)).ShouldEqual(DisplayName);
#else
            var serviceProvider = (IServiceProvider)SpyAttribute.ValidationContext.Items[DataAnnotationValidationProvider.ServiceProviderKey];
            serviceProvider.GetService(typeof(string)).ShouldEqual(DisplayName);
#endif
            SpyAttribute.ValidationContext.ObjectType.ShouldEqual(typeof(ContextValidatableModel));
            SpyAttribute.ValidationContext.ObjectInstance.ShouldEqual(model);
            SpyAttribute.ValidationContext.DisplayName.ShouldEqual(DisplayName);
        }

        /*  [TestMethod]
          public void TestGetDisplayName()
          {
              var type = typeof(SimpleValidatableModel);
              var provider = new DataAnnotationValidationProvider(new DisplayNameProvider(), null);
              string displayName;
              provider.TryGetDisplayName(type, "Property", out displayName).ShouldBeFalse();
              provider.TryGetDisplayName(type, "SecondProperty", out displayName).ShouldBeTrue();
              displayName.ShouldEqual(DisplayName);
          }

          [TestMethod]
          public void TestMetadataGetDisplayName()
          {
              var type = typeof(MetadataValidatableModel);
              var provider = new DataAnnotationValidationProvider(new DisplayNameProvider(), null);
              string displayName;
              provider.TryGetDisplayName(type, "Property", out displayName).ShouldBeTrue();
              displayName.ShouldEqual(DisplayName);
          }*/

        [TestMethod]
        public void FillErrorsTest()
        {
            var errors = new List<IValidationResult>();
            var dict = new Dictionary<string, IList<IValidationResult>>();
            errors.Add(new TestValidationResult("Test", "p", "p1"));
            errors.Add(new TestValidationResult("Test1", "p", "p1", "p2"));
            var provider = new DataAnnotationValidationProvider(new AnnotationDisplayNameProvider(), null);
            provider.FillErrors(errors, dict);
            dict.Count.ShouldEqual(3);
            dict["p"].Count.ShouldEqual(2);
            dict["p1"].Count.ShouldEqual(2);
            dict["p2"].Count.ShouldEqual(1);

            dict["p"].Any(result => result.ErrorMessage == "Test").ShouldBeTrue();
            dict["p"].Any(result => result.ErrorMessage == "Test1").ShouldBeTrue();

            dict["p1"].Any(result => result.ErrorMessage == "Test").ShouldBeTrue();
            dict["p1"].Any(result => result.ErrorMessage == "Test1").ShouldBeTrue();

            dict["p2"].Any(result => result.ErrorMessage == "Test1").ShouldBeTrue();
        }
    }
}